using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Net.NetworkInformation;


namespace Tswe.NETMF.Net
{
    public class UdpSocket : IDisposable
    {
        public static IPAddress BroadcastAddress
        {
            get { return new IPAddress(0xffffffffL); }
        }

        public UdpSocket() { }

        ~UdpSocket()
        {
            Dispose();
        }

        public string IPAddress
        {
            get { return iPAddress(); }
        }

        private string iPAddress()
        {
            // take the first network interface and return local IP
            foreach (var nif in NetworkInterface.GetAllNetworkInterfaces())
                return nif.IPAddress;
            // if no network available return null 
            return null;
        }

        public IPAddress LocalIPAddress
        {
            get { return localIPAddress(); }
        }

        private IPAddress localIPAddress()
        {
            var ipAddress = iPAddress();
            return ipAddress != null ? System.Net.IPAddress.Parse(ipAddress) : null;
        }

        public int LocalPort { get; set; }
        public bool Broadcast { get; set; }

        private Thread _workerThread;


        public event DataReceivedHandler DataReceived;
        public event SocketDoneHandler SocketDone;

        public bool Start()
        {
            if (_workerThread != null)
                return false;

            _workerThread = new Thread(new ThreadStart(threadStartTarget)); 
            _workerThread.Start();
            return true;
        }

        public bool Stop()
        {
            if (_workerThread == null)
                return false;

            _workerThread.Abort();
            return true;
        }

        private Socket _socket = null;

        private void threadStartTarget()
        {
            _socket = new Socket(AddressFamily.InterNetwork,
                SocketType.Dgram, ProtocolType.Udp);
            try
            {
                IPEndPoint broadcastEndPoint = new IPEndPoint(
                    System.Net.IPAddress.Any, LocalPort);
                
                _socket.SetSocketOption(SocketOptionLevel.Socket,
                    SocketOptionName.ReuseAddress, true);
                if (Broadcast)
                    _socket.SetSocketOption(SocketOptionLevel.Socket,
                        SocketOptionName.Broadcast, true);

                _socket.Bind(broadcastEndPoint);

                EndPoint remoteEndPoint = new IPEndPoint(
                    System.Net.IPAddress.Any, 0);

                while (true)
                {

                    int availableBytes = _socket.Available;
                    if (availableBytes > 0)
                    {
                        byte[] receiveBuffer = new byte[availableBytes];
                        _socket.ReceiveFrom(receiveBuffer, ref remoteEndPoint);

                        if (DataReceived != null)
                            DataReceived(this,
                                new DataReceivedEventArgs(
                                    receiveBuffer, remoteEndPoint));
                    }
                    // This is recommends in thread loops to help prevent lock-ups
                    Thread.Sleep(10);
                }
            }
            catch (Exception e)
            {
                // catch all exceptions
                Debug.Print(e.ToString());
            }
            finally
            {
                doDispose();
                if (SocketDone != null)
                    SocketDone(this, EventArgs.Empty);

                Debug.Print("UDP server thread finished.");
            }
        }

        public bool SendTo(string address, int port, byte[] data)
        {
            return SendTo(System.Net.IPAddress.Parse(address), port, data);
        }

        public bool SendTo(IPAddress address, int port, byte[] data)
        {
            return SendTo(new IPEndPoint(address, port), data);
        }

        public bool SendTo(IPEndPoint endPoint, byte[] data)
        {
            if (_socket == null)
                return false;
            _socket.SendTo(data, endPoint);
            return true;
        }

        #region IDisposable Members

        public void Dispose()
        {
            Stop();
        }

        private void doDispose()
        {
            _socket.Close();
            _socket = null;
        }

        #endregion
    }
}
